from __future__ import annotations

import json
import logging
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field

from openai import OpenAI

from ..config import get_settings
from ..deps import require_admin


logger = logging.getLogger(__name__)


router = APIRouter(
    prefix="/risk-planner",
    tags=["risk-planner"],
    dependencies=[Depends(require_admin)],
)


class SnapshotModel(BaseModel):
    """
    市场快照数据
    
    注意：ticker、positions、balance 必须是字典对象，不能是数字或字符串。
    """
    exchange: str = Field(..., description="交易所名称，如 'okx', 'binance', 'gate_io'")
    symbol: str = Field(..., description="交易对符号，如 'BTC-USDT', 'ETH-USDT'")
    ticker: Dict[str, Any] = Field(
        ...,
        description="最新价、24h数据等。必须是字典对象，例如：{'last': 65000.0, 'bid': 64999.0, 'ask': 65001.0, 'volume_24h': 1000000}",
        examples=[{"last": 65000.0, "bid": 64999.0, "ask": 65001.0, "volume_24h": 1000000}]
    )
    funding: Dict[str, Any] = Field(
        default_factory=dict,
        description="合约资金费率（仅衍生品）。现货可为空对象 {}。例如：{'rate': 0.0001, 'next_funding_time': '2025-11-03T08:00:00Z'}",
        examples=[{}, {"rate": 0.0001, "next_funding_time": "2025-11-03T08:00:00Z"}]
    )
    positions: Dict[str, Any] = Field(
        ...,
        description="该账户在该交易所的相关持仓。必须是字典对象，例如：{'BTC-USDT': {'size': 0.1, 'side': 'long', 'unrealized_pnl': 100}} 或 {}（无持仓）",
        examples=[{"BTC-USDT": {"size": 0.1, "side": "long", "unrealized_pnl": 100}}, {}]
    )
    balance: Dict[str, Any] = Field(
        ...,
        description="该账户余额，可用资金等。必须是字典对象，例如：{'BTC': {'available': 0.5, 'total': 0.5}, 'USDT': {'available': 10000, 'total': 10000}}",
        examples=[{"BTC": {"available": 0.5, "total": 0.5}, "USDT": {"available": 10000, "total": 10000}}]
    )
    candles: Optional[Any] = Field(
        default=None,
        description="K线数据数组，可选，元素包含 timestamp/open/high/low/close/volume 等"
    )


class RiskConfigModel(BaseModel):
    """风控配置"""
    allowed_exchanges: List[str] = Field(..., description="允许的交易所白名单")
    allowed_symbols: List[str] = Field(..., description="允许的交易对白名单")
    max_notional_usd: float = Field(..., description="最大名义金额（USD）")
    max_slippage_bps: int = Field(..., description="最大滑点（基点），如 50 表示 0.5%")
    max_daily_loss_usd: Optional[float] = Field(None, description="最大日亏损（USD）")
    enable_derivatives: bool = Field(True, description="是否允许衍生品交易")


class ContextModel(BaseModel):
    """上下文信息"""
    account_name: str = Field(..., description="账户名称，如 'test@test.com'")
    run_id: str = Field(..., description="运行标识，如 'runA'")
    timezone: str = Field("Asia/Shanghai", description="时区")
    mode: str = Field(..., description="运行模式：'shadow'（影子）| 'paper'（模拟）| 'live'（实盘）")


class RiskPlannerRequest(BaseModel):
    """
    风控规划器请求模型
    
    输入快照、风控配置和上下文，由 AI 规划器输出交易动作。
    """
    snapshot: SnapshotModel = Field(..., description="市场快照数据")
    risk_config: RiskConfigModel = Field(..., description="风控配置参数")
    context: ContextModel = Field(..., description="执行上下文信息")


class RiskPlannerResponse(BaseModel):
    """
    风控规划器响应模型
    
    输出唯一的动作 JSON，用于驱动后端执行或拒绝。
    """
    action: str = Field(
        ...,
        description="动作类型：'OPEN'（开仓）| 'CLOSE'（平仓）| 'CANCEL'（取消订单）| 'NOP'（不操作）",
        examples=["OPEN", "CLOSE", "CANCEL", "NOP"]
    )
    exchange: str = Field(..., description="交易所名称，必须在风险配置白名单内")
    symbol: str = Field(..., description="交易对符号，必须在风险配置白名单内")
    side: Optional[str] = Field(
        None,
        description="买卖方向：'buy'（买入）| 'sell'（卖出）| null（非 OPEN 动作为 null）",
        examples=["buy", "sell", None]
    )
    order_type: Optional[str] = Field(
        None,
        description="订单类型：'market'（市价）| 'limit'（限价）| null（非 OPEN 动作为 null）",
        examples=["market", "limit", None]
    )
    amount: Optional[float] = Field(
        None,
        description="币数量（base 币），如 0.01 BTC。OPEN/CLOSE 建议提供，其他为 null"
    )
    price: Optional[float] = Field(
        None,
        description="限价（报价币单位，如 USDT）。限价单必填，市价单为 null"
    )
    time_in_force: Optional[str] = Field(
        None,
        description="订单有效期：'GTC'（直到取消）| 'IOC'（立即或取消）| 'FOK'（全部或取消）| null",
        examples=["GTC", "IOC", "FOK", None]
    )
    account_name: str = Field(..., description="账户名称，必须等于 context.account_name")
    max_slippage_bps: Optional[int] = Field(
        None,
        description="最大滑点（基点），不给则由后端默认"
    )
    reason: Optional[str] = Field(
        None,
        description="决策依据/原因说明（最多 30 字），便于日志审计"
    )
    confidence: Optional[float] = Field(
        None,
        ge=0.0,
        le=1.0,
        description="置信度（0-1），表示决策的把握程度"
    )
    debug_trace: Optional[str] = Field(
        None,
        description="AI 推理过程完整文本，便于审计与回放"
    )
    reasoning_content: Optional[str] = Field(
        None,
        description="Reasoner 原始思维链文本"
    )
    content: Optional[str] = Field(
        None,
        description="Reasoner 最终回答文本"
    )


def _build_system_prompt() -> str:
    """构建风控规划器的系统提示词"""
    return """你是一个加密交易执行规划器（仅做"是否下什么单"的规划，不直接下单）。

你的角色：基于输入快照与风控配置，输出 1 个且仅 1 个"动作 JSON"，用于驱动后端执行或拒绝。

优先级：正确与安全 > 覆盖率 > 收益。有疑问就 NOP（不操作）。

## 输出格式要求

只输出一个 JSON 对象（无额外文字/注释/Markdown），格式如下：

{
  "action": "OPEN|CLOSE|CANCEL|NOP",
  "exchange": "<string, 必须 ∈ risk_config.allowed_exchanges>",
  "symbol": "<string, 必须 ∈ risk_config.allowed_symbols>",
  "side": "buy|sell|null",              // OPEN 必填，其余为 null
  "order_type": "market|limit|null",    // OPEN 必填，其余为 null
  "amount": <number|null>,              // 币数量（base），OPEN/CLOSE 建议给出，其余 null
  "price": <number|null>,               // 限价必填、单位=报价币（如 USDT）；市价为 null
  "time_in_force": "GTC|IOC|FOK|null",  // 未指定可默认 GTC；非 OPEN 为 null
  "account_name": "<string, 必须等于 context.account_name>",
  "max_slippage_bps": <integer|null>,   // 不给则由后端默认
  "reason": "<string|null>",            // 简述依据/原因（最多30字）
  "confidence": <number|null>,          // 0~1
  "debug_trace": "<string|null>"        // 详细推理过程，多段可用换行分隔
}

## 严格单位要求

- amount: 币数量（如 0.01 BTC），不是美元名义
- price: 报价货币（如 USDT）
- candles: 若提供，为按时间排序的 K 线列表，默认 interval=10m、days=7，可用于判断趋势/波动

## 决策规则（从上到下依次判定）

### 1. 白名单拦截
exchange 或 symbol 不在白名单 → NOP

### 2. 数据完整性
取不到最新价/余额/必要持仓信息 → NOP
若 candles 为空，仅说明行情不足，不能直接开仓，除非其他信号充分

### 3. 资金与名义额
计算名义额 amount × 最新价。若 > max_notional_usd → NOP
余额不足或仓位方向/数量不匹配目标动作 → NOP

### 4. 衍生品限制
若为合约/永续且 enable_derivatives=false → NOP

### 5. 滑点限制（仅限价单）
abs(price - 最新价) / 最新价 × 10_000 > max_slippage_bps → NOP

### 6. 重复/多动作
每次只允许一个动作。若出现多动作冲突 → 选安全优先：CANCEL > CLOSE > NOP > OPEN，最终只输出 1 个动作

### 7. 不确定性
信号矛盾、证据不足、关键信息缺失 → NOP

## 行为选择

### OPEN（开仓/加仓）
条件：满足以上风控；有方向性优势（基于 ticker 趋势/funding 变化/仓位空缺等给出理由）
必填：side、order_type、amount（price 仅限价）

### CLOSE（减仓/平仓）
条件：已有相应持仓且风险升高或信号转弱
建议提供 amount（未给则由后端按全量处理）

### CANCEL
仅当上下文已提供可取消的 order_id（一般通过后端传入）才返回；否则 NOP

### NOP
不满足任何安全且有把握的条件时，统一 NOP，并在 reason 写清原因（如"证据不足/超限/不在白名单"）

## 输出校验自检清单

✅ exchange/symbol/account_name 与输入一致并在白名单内
✅ action 合法；若 OPEN → side/order_type/amount 已填；限价则 price 已填
✅ 单位正确：amount=币数量、price=报价币
✅ 名义额 ≤ max_notional_usd；若超过 → 改为 NOP
✅ 限价偏离 ≤ max_slippage_bps；超过 → NOP
✅ 只输出一个 JSON；无额外文字/代码块/Markdown
✅ debug_trace 字段用字符串完整记录推理过程，可多段换行

## 异常与降级

- 输入缺字段/不可解析 → 统一 NOP
- 任意风控不通过 → NOP
- 不确定 → NOP
- 在 reason 简短说明原因（最多 30 字，便于日志审计）

## 重要提示

1. **必须只输出一个纯 JSON 对象**，不要有任何额外文本、Markdown 代码块、注释、说明文字
2. 响应格式必须是：直接以 `{` 开始，以 `}` 结束的完整 JSON
3. 有疑问就 NOP，安全第一
4. 所有单位与白名单严格按输入执行
5. reason 字段必须清晰说明决策原因

## 输出示例（请严格按照此格式输出，不要有任何其他文字）：

{"action":"NOP","exchange":"gate_io","symbol":"BTC-USDT","side":null,"order_type":null,"amount":null,"price":null,"time_in_force":null,"account_name":"test@test.com","max_slippage_bps":null,"reason":"数据不足","confidence":0.5,"debug_trace":"暂无明确信号，保持观望"}

⚠️ **再次强调**：你的响应必须是一个纯 JSON 对象，不能有任何前缀、后缀、说明文字或 Markdown 标记。直接输出 JSON，不要用 ```json 包裹。"""


def _validate_and_extract_json(response_text: str) -> Optional[Dict[str, Any]]:
    """从响应中提取并验证 JSON"""
    if not response_text or not response_text.strip():
        logger.warning("响应文本为空")
        return None
    
    response_text = response_text.strip()
    
    # 尝试直接解析 JSON
    try:
        result = json.loads(response_text)
        if isinstance(result, dict):
            logger.debug("成功直接解析 JSON")
            return result
    except json.JSONDecodeError as e:
        logger.debug(f"直接解析失败: {e}")
    
    # 尝试从代码块中提取
    import re
    json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', response_text, re.DOTALL)
    if json_match:
        try:
            result = json.loads(json_match.group(1))
            if isinstance(result, dict):
                logger.debug("成功从代码块中提取 JSON")
                return result
        except json.JSONDecodeError as e:
            logger.debug(f"代码块解析失败: {e}")
    
    # 尝试查找第一个完整的 JSON 对象（更健壮的匹配）
    # 匹配嵌套的 JSON 对象
    brace_count = 0
    start_idx = -1
    for i, char in enumerate(response_text):
        if char == '{':
            if brace_count == 0:
                start_idx = i
            brace_count += 1
        elif char == '}':
            brace_count -= 1
            if brace_count == 0 and start_idx >= 0:
                json_str = response_text[start_idx:i+1]
                try:
                    result = json.loads(json_str)
                    if isinstance(result, dict):
                        logger.debug("成功从文本中提取 JSON 对象")
                        return result
                except json.JSONDecodeError as e:
                    logger.debug(f"提取的 JSON 对象解析失败: {e}")
                start_idx = -1
    
    logger.warning(f"所有 JSON 提取方法均失败，响应文本: {response_text[:200]}...")
    return None


@router.post(
    "/plan",
    response_model=RiskPlannerResponse,
    summary="风控规划器",
    description="""
基于市场快照、风控配置和上下文，输出交易动作规划。

## 功能说明

- **输入**: 市场快照、风控配置、执行上下文
- **输出**: 唯一的动作 JSON（OPEN/CLOSE/CANCEL/NOP）
- **原则**: 安全第一，有疑问就 NOP

## 输入数据格式要求

⚠️ **重要**: `snapshot` 中的 `ticker`、`positions`、`balance` 必须是字典对象，不能是数字或字符串。

### 正确的数据结构示例：

```json
{
  "snapshot": {
    "exchange": "gate_io",
    "symbol": "BTC-USDT",
    "ticker": {
      "last": 65000.0,
      "bid": 64999.0,
      "ask": 65001.0,
      "volume_24h": 1000000
    },
    "funding": {},
    "positions": {
      "BTC-USDT": {
        "size": 0.1,
        "side": "long"
      }
    },
    "balance": {
      "USDT": {
        "available": 10000,
        "total": 10000
      }
    }
  },
  "risk_config": {
    "allowed_exchanges": ["okx", "binance", "gate_io"],
    "allowed_symbols": ["BTC-USDT", "ETH-USDT"],
    "max_notional_usd": 1000,
    "max_slippage_bps": 50,
    "max_daily_loss_usd": 300,
    "enable_derivatives": true
  },
  "context": {
    "account_name": "test@test.com",
    "run_id": "runA",
    "timezone": "Asia/Shanghai",
    "mode": "paper"
  }
}
```

## 决策流程

1. 白名单检查
2. 数据完整性验证
3. 资金与名义额校验
4. 衍生品限制检查
5. 滑点限制（限价单）
6. 动作冲突处理（安全优先）
7. 不确定性处理

## 输出格式

严格按照 RiskPlannerResponse 模型输出，只包含 JSON 对象，无额外文本。

参考规范文档中的测试用例进行验证。
    """,
        responses={
        200: {
            "description": "成功返回动作规划",
            "content": {
                "application/json": {
                    "examples": {
                        "NOP": {
                            "summary": "不操作示例",
                            "value": {
                                "action": "NOP",
                                "exchange": "gate_io",
                                "symbol": "BTC-USDT",
                                "side": None,
                                "order_type": None,
                                "amount": None,
                                "price": None,
                                "time_in_force": None,
                                "account_name": "test@test.com",
                                "max_slippage_bps": None,
                                "reason": "数据不足，无法确定操作",
                                "confidence": 0.5
                            }
                        },
                        "OPEN_MARKET": {
                            "summary": "市价开仓示例",
                            "value": {
                                "action": "OPEN",
                                "exchange": "gate_io",
                                "symbol": "BTC-USDT",
                                "side": "buy",
                                "order_type": "market",
                                "amount": 0.01,
                                "price": None,
                                "time_in_force": "GTC",
                                "account_name": "test@test.com",
                                "max_slippage_bps": 50,
                                "reason": "趋势向上，余额充足，名义额合规",
                                "confidence": 0.8
                            }
                        },
                        "OPEN_LIMIT": {
                            "summary": "限价开仓示例",
                            "value": {
                                "action": "OPEN",
                                "exchange": "gate_io",
                                "symbol": "BTC-USDT",
                                "side": "buy",
                                "order_type": "limit",
                                "amount": 0.01,
                                "price": 64900.0,
                                "time_in_force": "GTC",
                                "account_name": "test@test.com",
                                "max_slippage_bps": 50,
                                "reason": "限价买入，滑点合规",
                                "confidence": 0.75
                            }
                        },
                        "CLOSE": {
                            "summary": "平仓示例",
                            "value": {
                                "action": "CLOSE",
                                "exchange": "gate_io",
                                "symbol": "BTC-USDT",
                                "side": None,
                                "order_type": None,
                                "amount": 0.01,
                                "price": None,
                                "time_in_force": None,
                                "account_name": "test@test.com",
                                "max_slippage_bps": None,
                                "reason": "风险升高，建议减仓",
                                "confidence": 0.7
                            }
                        }
                    }
                }
            }
        },
        400: {"description": "请求参数错误"},
        500: {"description": "服务器错误或 AI 模型响应异常"}
    }
)
async def plan_action(req: RiskPlannerRequest) -> RiskPlannerResponse:
    """
    风控规划器主入口
    
    接收快照、风控配置和上下文，返回交易动作规划。
    """
    # 1. 基础验证
    if not req.snapshot or not req.risk_config or not req.context:
        raise HTTPException(
            status_code=400,
            detail="snapshot、risk_config、context 三段对象均需存在"
        )
    
    # 2. 白名单预检查（快速失败）
    if req.snapshot.exchange not in req.risk_config.allowed_exchanges:
        return RiskPlannerResponse(
            action="NOP",
            exchange=req.snapshot.exchange,
            symbol=req.snapshot.symbol,
            side=None,
            order_type=None,
            amount=None,
            price=None,
            time_in_force=None,
            account_name=req.context.account_name,
            max_slippage_bps=None,
            reason="交易所不在白名单",
            confidence=1.0
        )
    
    if req.snapshot.symbol not in req.risk_config.allowed_symbols:
        return RiskPlannerResponse(
            action="NOP",
            exchange=req.snapshot.exchange,
            symbol=req.snapshot.symbol,
            side=None,
            order_type=None,
            amount=None,
            price=None,
            time_in_force=None,
            account_name=req.context.account_name,
            max_slippage_bps=None,
            reason="交易对不在白名单",
            confidence=1.0
        )
    
    # 3. 准备 DeepSeek 客户端
    settings = get_settings()
    
    if not settings.deepseek_api_key:
        raise HTTPException(
            status_code=500,
            detail="DEEPSEEK_API_KEY 未配置。请在 .env 文件中设置 DEEPSEEK_API_KEY。"
        )
    
    client = OpenAI(
        api_key=settings.deepseek_api_key,
        base_url=settings.deepseek_base_url,
    )
    model = settings.deepseek_model
    
    # 4. 构建请求消息
    system_prompt = _build_system_prompt()
    
    user_message = {
        "snapshot": req.snapshot.model_dump(),
        "risk_config": req.risk_config.model_dump(),
        "context": req.context.model_dump()
    }
    
    # 5. 调用 AI 模型
    try:
        user_prompt = json.dumps(user_message, ensure_ascii=False, indent=2)
        
        logger.debug(f"发送给 AI 的提示词长度: {len(system_prompt)} 字符")
        logger.debug(f"用户消息长度: {len(user_prompt)} 字符")
        
        # 检查提示词长度
        total_prompt_length = len(system_prompt) + len(user_prompt)
        logger.info(f"总提示词长度: {total_prompt_length} 字符 (system: {len(system_prompt)}, user: {len(user_prompt)})")
        
        # DeepSeek Reasoner 模型特性：需要 tokens 用于推理思考，然后才是最终输出
        # 从日志看，2000 tokens 全部用于 reasoning，导致最终 content 为空
        # 需要大幅增加 max_tokens，确保推理和输出都有足够空间
        # Reasoner 可能需要：推理 tokens (2000-4000) + 输出 tokens (500-1000)
        estimated_max_tokens = 5000  # 大幅增加，确保推理和输出都有足够空间
        
        # 如果是 Reasoner 模型，可能需要等待思考过程
        # 检查模型名称
        is_reasoner = "reasoner" in model.lower()
        
        try:
            response = client.chat.completions.create(
                model=model,
                messages=[
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": user_prompt}
                ],
                temperature=0.1,  # 低温度确保确定性
                max_tokens=estimated_max_tokens,
                timeout=60.0 if is_reasoner else 30.0,  # Reasoner 需要更长时间
            )
        except Exception as api_error:
            logger.error(f"API 调用失败: {api_error}")
            logger.error(f"错误类型: {type(api_error)}")
            raise
        
        # 检查响应对象
        if not response or not response.choices:
            logger.error("AI 响应对象为空或没有 choices")
            raise ValueError("AI 响应对象无效")
        
        choice = response.choices[0]
        response_text = choice.message.content if choice.message else ""
        debug_trace: Optional[str] = None
        reasoning_content_text: Optional[str] = None

        def _format_reasoning(reasoning_data: Any) -> str:
            try:
                if isinstance(reasoning_data, str):
                    return reasoning_data
                if isinstance(reasoning_data, list):
                    segments: List[str] = []
                    for item in reasoning_data:
                        if isinstance(item, str):
                            segments.append(item)
                        else:
                            item_dict: Any
                            if hasattr(item, "model_dump"):
                                item_dict = item.model_dump(exclude_none=True)
                            elif isinstance(item, dict):
                                item_dict = item
                            else:
                                item_dict = json.loads(json.dumps(item, default=lambda o: getattr(o, "__dict__", str(o))))
                            text_value = item_dict.get("text") or item_dict.get("content") or item_dict.get("summary")
                            if isinstance(text_value, list):
                                segments.extend(str(v) for v in text_value if v)
                            elif text_value:
                                segments.append(str(text_value))
                            else:
                                segments.append(json.dumps(item_dict, ensure_ascii=False))
                    return "\n".join(segments)
                if hasattr(reasoning_data, "model_dump"):
                    return json.dumps(reasoning_data.model_dump(exclude_none=True), ensure_ascii=False)
                return json.dumps(reasoning_data, ensure_ascii=False, default=str)
            except Exception as format_err:
                logger.debug(f"格式化 reasoning 失败: {format_err}")
                return json.dumps(reasoning_data, ensure_ascii=False, default=str)

        try:
            if choice and getattr(choice, "message", None):
                message_obj = choice.message
                reasoning_attr = getattr(message_obj, "reasoning", None)
                if reasoning_attr:
                    formatted = _format_reasoning(reasoning_attr)
                    debug_trace = formatted
                    reasoning_content_text = formatted
                reasoning_content_attr = getattr(message_obj, "reasoning_content", None)
                if reasoning_content_attr:
                    reasoning_content_text = _format_reasoning(reasoning_content_attr)
                    if not debug_trace:
                        debug_trace = reasoning_content_text
                elif hasattr(message_obj, "model_dump"):
                    dump = message_obj.model_dump(exclude_none=True)
                    if dump.get("reasoning"):
                        formatted = _format_reasoning(dump["reasoning"])
                        debug_trace = formatted
                        reasoning_content_text = formatted
                    elif dump.get("reasoning_content"):
                        reasoning_content_text = _format_reasoning(dump["reasoning_content"])
                        if not debug_trace:
                            debug_trace = reasoning_content_text
            if not debug_trace and hasattr(choice, "model_dump"):
                choice_dump = choice.model_dump(exclude_none=True)
                reasoning_candidate = choice_dump.get("message", {}).get("reasoning") if isinstance(choice_dump, dict) else None
                if reasoning_candidate:
                    formatted = _format_reasoning(reasoning_candidate)
                    debug_trace = formatted
                    reasoning_content_text = formatted
                reasoning_candidate_alt = choice_dump.get("message", {}).get("reasoning_content") if isinstance(choice_dump, dict) else None
                if reasoning_candidate_alt:
                    reasoning_content_text = _format_reasoning(reasoning_candidate_alt)
                    if not debug_trace:
                        debug_trace = reasoning_content_text
        except Exception as trace_error:
            logger.warning(f"提取 AI 推理过程失败: {trace_error}")

        if not reasoning_content_text and debug_trace:
            reasoning_content_text = debug_trace

        if not debug_trace and reasoning_content_text:
            debug_trace = reasoning_content_text

        if not debug_trace and response_text:
            debug_trace = response_text
        
        # 记录响应元数据
        logger.info(f"AI 响应长度: {len(response_text)} 字符")
        logger.info(f"Finish reason: {getattr(choice, 'finish_reason', 'unknown')}")
        if hasattr(response, 'usage') and response.usage:
            logger.info(f"Token 使用: prompt={response.usage.prompt_tokens}, completion={response.usage.completion_tokens}, total={response.usage.total_tokens}")
        
        if response_text:
            logger.debug(f"AI 响应内容（前500字符）: {response_text[:500]}")
        else:
            logger.warning("AI 响应内容为空！")
            logger.warning(f"Response object: {response}")
            logger.warning(f"Choice object: {choice}")
            logger.warning(f"Message object: {choice.message if choice else None}")
        
        # 6. 提取 JSON
        action_json = _validate_and_extract_json(response_text)
        
        if not action_json:
            logger.warning(f"无法从响应中提取 JSON")
            logger.warning(f"完整响应内容: {repr(response_text)}")
            finish_reason = getattr(choice, 'finish_reason', 'unknown')
            
            # 根据 finish_reason 提供更详细的错误信息
            if not response_text:
                reason_msg = f"AI 未返回内容（finish_reason: {finish_reason}）"
            elif finish_reason == "length":
                reason_msg = f"AI 响应被截断（超过 {response.usage.completion_tokens if hasattr(response, 'usage') and response.usage else '?'} tokens）"
            else:
                reason_msg = f"AI 响应格式错误（长度: {len(response_text)}, finish_reason: {finish_reason}）"
            
            return RiskPlannerResponse(
                action="NOP",
                exchange=req.snapshot.exchange,
                symbol=req.snapshot.symbol,
                side=None,
                order_type=None,
                amount=None,
                price=None,
                time_in_force=None,
                account_name=req.context.account_name,
                max_slippage_bps=None,
                reason=reason_msg[:30],  # 限制长度
                confidence=0.0,
                debug_trace=debug_trace,
                reasoning_content=reasoning_content_text,
                content=response_text
            )
        
        # 7. 验证并转换为响应模型
        try:
            # 确保关键字段存在
            if "action" not in action_json:
                action_json["action"] = "NOP"
            if "exchange" not in action_json:
                action_json["exchange"] = req.snapshot.exchange
            if "symbol" not in action_json:
                action_json["symbol"] = req.snapshot.symbol
            if "account_name" not in action_json:
                action_json["account_name"] = req.context.account_name
            
            # 验证白名单（再次确认）
            if action_json.get("exchange") not in req.risk_config.allowed_exchanges:
                action_json["action"] = "NOP"
                action_json["reason"] = "输出交易所不在白名单"
            if action_json.get("symbol") not in req.risk_config.allowed_symbols:
                action_json["action"] = "NOP"
                action_json["reason"] = "输出交易对不在白名单"
            if debug_trace:
                action_json["debug_trace"] = debug_trace
            if reasoning_content_text and "reasoning_content" not in action_json:
                action_json["reasoning_content"] = reasoning_content_text
            if response_text and "content" not in action_json:
                action_json["content"] = response_text
            logger.info("AI 动作 JSON: %s", json.dumps(action_json, ensure_ascii=False))
            
            return RiskPlannerResponse(**action_json)
            
        except Exception as e:
            logger.error(f"解析 AI 响应为 RiskPlannerResponse 失败: {e}, JSON: {action_json}")
            return RiskPlannerResponse(
                action="NOP",
                exchange=req.snapshot.exchange,
                symbol=req.snapshot.symbol,
                side=None,
                order_type=None,
                amount=None,
                price=None,
                time_in_force=None,
                account_name=req.context.account_name,
                max_slippage_bps=None,
                reason=f"响应验证失败: {str(e)[:30]}",
                confidence=0.0,
                debug_trace=debug_trace,
                reasoning_content=reasoning_content_text,
                content=response_text
            )
    
    except Exception as e:
        logger.error(f"调用 AI 模型失败: {e}")
        raise HTTPException(
            status_code=500,
            detail=f"AI 模型调用失败: {str(e)}"
        )
